# Created by 黄景涛
# DATE: 2024/11/6
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By

from utils.log_helper.logger import logger
from base.kdt_lib.base_mixin import BaseMiXin


class Canvas_V3(BaseMiXin):
    """ ITSM-V3.5的流程画布 """

    # 动作：拖拽节点
    def key_drag_node(self, node_name, off_x, off_y):
        """
        :param self:
        :param node_name: 节点类型，中文名称
        :param off_x: 相对于画布的开始节点，x坐标，e.g: 100
        :param off_y: 相对于画布的开始节点，y坐标，e.g: 200
        :return:
        """
        # 各类卡片的定位信息
        node_data_types = {
            "START": "开始节点",
            "END": "结束节点",
            "NORMAL": "手动节点",
            "TASK": "API节点",
            "TASK-SOPS": "运维节点",
            "SIGN": "会签节点",
            "APPROVAL": "审批节点",
            "ROUTER-P": "并行网关",
            "COVERAGE": "汇聚网关",
        }

        # 生成XPATH路径（也可以用其他定位方式）
        for k, v in node_data_types.items():
            if node_name.strip() in v:
                xpath_loc = f'//*[@data-type="{k}"]'  # 仅ITSM-V3.x适用
                break
        else:
            raise Exception(f'未找到【{node_name}】类型的节点！')

        def drag_and_drop_to_offset(element, offset_x, offset_y):
            """ 拖拽到指定位置释放 """
            action = ActionChains(self.driver)
            # 拖放到指定位置
            action.click_and_hold(element).move_by_offset(offset_x, offset_y).release().perform()

        # 以【开始】节点作为参照定位
        start_loc = '.startpoint'  # ITSM-V3.x适用
        start_node = self.find_element(start_loc)
        # 获取开始节点的xy坐标
        start_x = start_node.location['x']
        start_y = start_node.location['y']

        # 定位节点卡片
        node_ele = self.find_element(xpath_loc)
        node_x = node_ele.location['x']
        node_y = node_ele.location['y']

        # 在画布中的位置计算
        target_x = start_x + off_x - node_x
        target_y = start_y + off_y - node_y
        # 拖拽节点到画布
        drag_and_drop_to_offset(node_ele, target_x, target_y)

    # 动作：线条连线
    def key_line_connect(self, source_, target_):
        """
        :param self:
        :param source_: 源节点名称和连接点，  e.g: 提单,右   --连接点分四个方向：上下左右
        :param target_: 目标节点名称和连接点，  e.g: 新增节点,左
        :return:
        """

        def up_down_left_right_by_relative(node):
            """ 计算上下左右连接点，相对节点的偏移坐标 """

            # 从WebElement实例，获取属性：节点卡片的宽、高
            width, height = node.rect['width'], node.rect['height']
            # 余量，目前连接点是个圆圈，尺寸16*16，半径8
            margin_size = 5
            up = (0, -(height // 2 + margin_size))
            down = (0, (height // 2 + margin_size))
            left = (-(width // 2 + margin_size), 0)
            right = ((width // 2 + margin_size), 0)
            sides_info = {"上": up, "下": down, "左": left, "右": right}
            return sides_info

        def node_ele(node_name):
            """ 从画布中，按照节点名称定位节点 """
            nonlocal self
            # 定位画布
            canvas = self.driver.find_element(By.ID, 'canvas-flow')  # 仅ITSM-3.x适用
            # 查找所有ID包含“node”的子级div
            nodes = canvas.find_elements(By.XPATH, './/div[contains(@id, "node")]')  # 仅ITSM-3.x适用
            for node in nodes:
                demo = node.text
                if node.text == node_name or node_name in node.text:
                    return node
            logger.error(f'未找到【{node_name}】节点')

        # excel中的参数：e.g, 提单,右
        source_node_name, source_node_side = source_.strip().replace('，', ',').split(',')
        target_node_name, target_node_side = target_.strip().replace('，', ',').split(',')

        # 源、目标节点对象
        source_node = node_ele(source_node_name)
        target_node = node_ele(target_node_name)

        # 节点的连接点，相对偏移坐标
        source_side_rel_xy = up_down_left_right_by_relative(source_node).get(source_node_side)
        target_side_rel_xy = up_down_left_right_by_relative(target_node).get(target_node_side)

        actions = ActionChains(self.driver)
        # 模拟从源节点偏移位，到目标节点偏移位的拖拽操作
        actions.move_to_element_with_offset(source_node, *source_side_rel_xy).click_and_hold()
        actions.move_to_element_with_offset(target_node, *target_side_rel_xy).release()
        actions.perform()

    # 动作：点击节点
    def key_click_node(self, node_name: str):
        """ 点击进入节点详情 """

        def node_ele(node_name):
            """ 从画布中，按照节点名称定位节点 """
            nonlocal self
            # 定位画布
            canvas = self.driver.find_element(By.ID, 'canvas-flow')  # 仅ITSM-3.x适用
            # 查找所有ID包含“node”的子级div
            nodes = canvas.find_elements(By.XPATH, './/div[contains(@id, "node")]')  # 仅ITSM-3.x适用
            for node in nodes:
                demo = node.text
                if node.text == node_name or node_name in node.text:
                    return node
            logger.error(f'未找到【{node_name}】节点')

        node = node_ele(node_name)
        node.click()

    # 动作：点击线条
    def key_click_line(self, line_name: str):
        """ 点击进入线条详情 """

        def node_ele(node_name):
            """ 从画布中，按照节点名称定位节点 """
            nonlocal self
            # 定位画布
            canvas = self.driver.find_element(By.ID, 'canvas-flow')  # 仅ITSM-3.x适用
            # 查找所有ID包含“node”的子级div
            nodes = canvas.find_elements(By.XPATH, './/div[contains(@id, "jsPlumb")]')  # 仅ITSM-3.x适用
            for node in nodes:
                demo = node.text
                span_node = node.find_element(By.TAG_NAME, "span")
                if span_node.text == node_name or span_node.text.startswith(node_name):
                    return span_node
            logger.error(f'未找到【{node_name}】线条')

        node = node_ele(line_name)
        node.click()
